﻿namespace MailChimp.Types

open System
open System.Collections
open System.Collections.Generic
open System.Net
open System.Runtime.InteropServices
open System.Xml
open System.Xml.Serialization

//==================================================================================
//  Common
//==================================================================================

[<AutoOpen>]
/// <summary>Provides common functions used across the application.</summary>
module internal Common =

    open System.Text.RegularExpressions

    /// <summary>Gets an embedded resource.</summary>
    val getResource : string -> System.IO.Stream

    /// <summary>Reads a stream from the current position to the end.</summary>
    val readStream : System.IO.Stream -> string

    /// <summary>Indicates whether the specified string is null or empty.</summary>
    val inline isNullOrEmpty : string -> bool

    /// <summary>Removes all leading and trailing white-space characters
    /// from the current String object.</summary>
    val inline trim : string -> string

    /// <summary>Creates regex from pattern.</summary>
    val inline regex : string -> Regex

    /// <summary>Replaces strings that match a regular expression pattern
    /// with the specified replacement string.</summary>
    val inline replace : Regex -> string -> string -> string

    /// <summary>Gets the lowercase value of the attribute.</summary>
    val xattr : string -> XmlReader -> string

    /// <summary>Converts string to specified type.</summary>
    val convert : string -> 'T

    /// <summary>Converts <c>System.DayOfWeek</c> to ISO-8601 weekday number.</summary>
    val isoWeekDay : DayOfWeek -> int

    /// <summary>Encodes string into a sequence of bytes using UTF8 format.</summary>
    val getBytesUTF8 : string -> byte[]

    /// <summary>Encodes string into a sequence of bytes asing ASCII format.</summary>
    val getBytesASCII : string -> byte[]

    //------------------------------------------------------------------------------
    //  (*) Serialization
    //------------------------------------------------------------------------------

    /// <summary>Creates an Xsl Transform from the embedded resource.</summary>
    val getTransform : Lazy<Xsl.XslCompiledTransform>

    /// <summary>Sorts an xml document.</summary>
    val prepareXml : string -> string

    /// <summary>Creates XmlDictionaryReader from Json string.</summary>
    val createJsonReader : string -> XmlDictionaryReader

    /// <summary>Deserializes the specified object using Xml DataContractSerializer.</summary>
    val deserializeXml : obj -> 'T

    /// <summary>Deserializes the specified object using JSon DataContractSerializer.</summary>
    val deserializeJson : obj -> 'T

//------------------------------------------------------------------------------
//  (*) Type abbreviations
//------------------------------------------------------------------------------

/// <summary>HttpStatusCode abbreviation.</summary>
type internal httpst = HttpStatusCode

/// <summary>Optional param attribute abbreviation.</summary>
type internal op = OptionalAttribute

/// <summary>Default value attribute abbreviation.</summary>
type internal dv = DefaultParameterValueAttribute

//==================================================================================
//  Input
//==================================================================================

[<Class>]
/// <summary>Dictionary for the input data.</summary>
type Input =
    inherit System.Collections.Generic.Dictionary<string, obj>

    new : unit -> Input

    /// <summary>Removes item with the specified key.</summary>
    member Remove : key:string -> unit

    /// <summary>Merges current object with another one.</summary>
    /// <remarks>This will replace items with duoplicate keys.</remarks>
    member Merge : p:Input -> unit

    /// <summary>Merges current object with another one implementing IInput interface.</summary>
    /// <remarks>This will replace items with duplicate keys.</remarks>
    member Merge : p:IInput -> unit
and
 IInput =
    abstract ToInput : unit -> Input


/// <summary>Interface used to convert an object into query string parameter.</summary>
type internal IValue =
    abstract member Value : obj


/// <summary>Internal helper methods.</summary>
module internal InputHelper =

    /// <summary>Converts date to string formatted as "yyyy:MM:dd".</summary>
    val dateStr : DateTime -> string

    /// <summary>Converts date/time to string formatted as "yyyy-MM-dd HH:mm:ss".</summary>
    val dateTimeStr : DateTime -> string

    /// <summary>Creates delimited string from a sequence.</summary>
    /// <remarks>When delimiter is comma, the method escapes all commas in the input strings.
    /// <para>This behavior can be overridden by postpending commas with backslash - ",\".</para></remarks>
    val joinSeq : string -> 'T seq -> string

    /// <summary>Splits string into an array using the specified delimiter.</summary>
    /// <remarks>When delimiter is comma, the method skips escaped commas and
    /// then unascapes them in the resulting array.
    /// <para>This behavior can be overridden by postpending commas with backslash - ",\".</para></remarks>
    val split : string -> string -> 'T[]

    /// <summary>Creates an Input object.</summary>
    /// <remarks>Uses reflection and DataMember attributes to get keys and values
    /// to be inserted into the Input object.</remarks>
    val build : obj -> Input


[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
/// <summary>Provides operations on Input.</summary>
module Input =

    /// <summary>Creates an Input object from a dictionary with the specified key function.</summary>
    val ofDict : keyf:(string -> string) -> xd:IDictionary -> Input

    /// <summary>Creates an Input object from a sequence.</summary>
    val ofSeq : xs:IEnumerable -> Input

    /// <summary>Generates a URL-encoded query string.</summary>
    val buildQueryString : Input -> string

//==================================================================================
//  Helper types
//==================================================================================

[<DefaultAugmentation(false)>]
[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
/// <summary>The type of optional values that implements explicit/implicit convertion operators.</summary>
type Opt<'T> =
    /// <summary>The representation of "No value"</summary>
    | None
    /// <summary>The representation of "Value of type 'T"</summary>
    | Some of 'T

    interface IValue

    [<CompilationRepresentation(CompilationRepresentationFlags.Instance)>]
    /// <summary>Get the value of a 'Some' option.</summary>
    /// <exception cref="System.NullReferenceException">Thrown when the option is 'None'.</exception>
    member Value : 'T

    /// <summary>Explicit convertion operator.</summary>
    /// <exception cref="System.NullReferenceException">Thrown when the option is 'None'.</exception>
    static member op_Explicit : x:'T opt -> 'T

    /// <summary>Implicit convertion operator.</summary>
    static member op_Implicit : x:'T -> 'T opt

    /// <summary>Creates an option value that is a 'None' value.</summary>
    static member None : 'T opt

    /// <summary>Creates an option value that is a 'Some' value.</summary>
    static member Some : x:'T -> 'T opt

/// <summary>Alternative to FSharp.Option implementing explicit/implicit convertion operators.</summary>
and 'T opt = Opt<'T>


[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
/// <summary>Provides operations on Opt.</summary>
module Opt =

    /// <summary>Creates an option value that is a 'Some' value.</summary>
    val Some : 'T -> 'T opt

    /// <summary>Checks whether the option is a 'Some' value.</summary>
    val IsNone : 'T opt  -> bool

    /// <summary>Checks whether the option is a 'None' value.</summary>
    val IsSome : 'T opt  -> bool

    /// <summary>Gets the value of a 'Some' option.</summary>
    val GetValue : 'T opt -> 'T

    /// <summary>Converts opt to a FSharp.Option object.</summary>
    val toOption : 'T opt -> 'T option

    /// <summary>Converts FSharp.Option to an opt object.</summary>
    val ofOption : 'T option -> 'T opt


[<Struct>]
[<CustomEquality; CustomComparison>]
/// <summary>Represents an object whose underlying type is a value
/// type that can also be assigned a null reference like a reference type.
/// <para>Implements IXmlSerializable interface.</para></summary>
type MCNull<'T> when 'T : struct =
    interface System.IComparable
    interface IValue
    interface IXmlSerializable

    val mutable private value : 'T
    val mutable private hasValue : bool

    new : v:'T -> 'T mcnull

    /// <summary>Gets a value indicating whether the current object has a value.</summary>
    member HasValue : bool

    /// <summary>Gets the value of the current object.</summary>
    member Value : 'T

    /// <summary>Explicit convertion operator.</summary>
    static member op_Explicit : x:'T mcnull -> 'T

    /// <summary>Implicit convertion operator.</summary>
    static member op_Implicit : x:'T -> 'T mcnull


/// <summary>Represents an object whose underlying type is a value
/// type that can also be assigned a null reference like a reference type.
/// <para>Implements IXmlSerializable interface.</para></summary>
and 'T mcnull when 'T : struct = MCNull<'T>


[<Struct>]
/// <summary>Boolean implementing IXmlSerializable interface.</summary>
type MCBool =
    interface IXmlSerializable

    /// <summary>Gets the value of the current object.</summary>
    val mutable Value : bool

    new : bool -> MCBool

    /// <summary>Explicit convertion operator.</summary>
    static member op_Explicit : x:MCBool -> bool

    /// <summary>Implicit convertion operator.</summary>
    static member op_Implicit : x:bool -> MCBool


[<Struct>]
/// <summary>DateTime implementing IXmlSerializable interface.</summary>
type MCDateTime =
    interface IXmlSerializable

    /// <summary>Gets the value of the current object.</summary>
    val mutable Value : DateTime

    new : v:DateTime -> MCDateTime

    // <summary>Explicit convertion operator.</summary>
    static member op_Explicit : x:MCDateTime -> DateTime

    /// <summary>Implicit convertion operator.</summary>
    static member op_Implicit : x:DateTime -> MCDateTime


/// <summary>Line reader class.</summary>
type internal LineReader =
    inherit IO.StreamReader

    new : s:IO.Stream -> LineReader

    /// <summary>Gets the current line number.</summary>
    member LineNum : int

    /// <summary>Reads a line from the underlying string.</summary>
    override ReadLine : unit -> string

//------------------------------------------------------------------------------
//  (*) Operators
//------------------------------------------------------------------------------

[<AutoOpen>]
/// <summary>Module containing common operators.</summary>
module internal Operators =

    /// <summary>Converts opt to FSharp option.</summary>
    val (>?) : 'T opt -> 'T option

    /// <summary>Converts FSharp option to opt.</summary>
    val (<?) : 'T option -> 'T opt

    /// <summary>Replaces null object with the specified value.</summary>
    val (@?) : 'T -> 'T -> 'T when 'T:equality and 'T:null

    /// <summary>Obj quality operator.</summary>
    val (==) : 'T -> 'V -> bool

    /// <summary>Obj inequality operator.</summary>
    val (!=) : 'T -> 'V -> bool

    /// <summary>Used to specify a default value for an optional argument
    /// in the implementation of a function.</summary>
    val defValue : 'T opt -> 'T -> 'T

//------------------------------------------------------------------------------
//  (*) Specialized collections
//------------------------------------------------------------------------------

[<DefaultAugmentation(false)>]
[<CompilationRepresentation(CompilationRepresentationFlags.UseNullAsTrueValue)>]
/// <summary>The union type representing specialized sequence.
/// It's string representation depends on the underlying type:
/// <para>- Singleton value is converted as is;</para>
/// <para>- Sequence value is converted to a comma delimited string.</para></summary>
/// <remarks>Implements explicit/implicit convertion operators.</remarks>
type MCSeq<'T> =
    /// <summary>The representation of "No value"</summary>
    | Null
    /// <summary>The representation of a singleton</summary>
    | Sn of 'T
    /// <summary>The representation of a sequence</summary>
    | Sq of 'T seq

    interface IValue

    [<CompilationRepresentation(CompilationRepresentationFlags.Instance)>]
    /// <summary>Gets the value represented by 'Sn' option (<c>'T</c>).</summary>
    /// <exception cref="System.InvalidOperationException">Thrown when the value is not 'Sn' type.</exception>
    member SnValue : 'T

    [<CompilationRepresentation(CompilationRepresentationFlags.Instance)>]
    /// <summary>Gets the value represented by 'Sq' option (<c>Seq<'T></c>).</summary>
    /// <exception cref="System.InvalidOperationException">Thrown when the value is not 'Sq' type.</exception>
    member SqValue : 'T seq

    /// <summary>Implicit convertion from <c>'T</c>.</summary>
    static member op_Implicit : x:'T -> 'T mcseq

    /// <summary>Implicit convertion from <c>Seq<'T></c>.</summary>
    static member op_Implicit : x:'T seq -> 'T mcseq

    /// <summary>Explicit convertion to <c>'T</c>.</summary>
    /// <exception cref="System.InvalidOperationException">Thrown when the value is not 'Sn' type.</exception>
    static member op_Explicit : x:'T mcseq -> 'T

    /// <summary>Explicit convertion to <c>Seq<'T></c>.</summary>
    /// <exception cref="System.InvalidOperationException">Thrown when the value is not 'Sq' type.</exception>
    static member op_Explicit : x:'T mcseq -> 'T seq

    /// <summary>Creates a MCSeq value that is a 'Null' value.</summary>
    static member Null : 'T mcseq

    /// <summary>Creates a MCSeq value that is a 'Sn' value.</summary>
    static member Create : x:'T -> 'T mcseq

    /// <summary>Creates a MCSeq value that is a 'Sq' value.</summary>
    static member Create : x:'T seq -> 'T mcseq

    /// <summary>Returns a comma delimited String that represents the current MCSeq object.</summary>
    /// <remarks>The method escapes all commas unless they are postpended with backslash - ",\".</remarks>
    override ToString : unit -> string

/// <summary>The union type representing specialized sequence.
/// It's string representation depends on the underlying type:
/// <para>- Singleton value is converted as is;</para>
/// <para>- Sequence value is converted to a comma delimited string.</para></summary>
/// <remarks>Implements explicit/implicit convertion operators.</remarks>
and 'T mcseq = MCSeq<'T>


[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
/// <summary>Provides operations on MCSeq.</summary>
module MCSeq =

    /// <summary>Creates a MCSeq value that is a 'Sn' value.</summary>
    val Sn : 'T -> 'T mcseq

     /// <summary>Creates a MCSeq value that is a 'Sq' value.</summary>
    val Sq : 'T seq -> 'T mcseq

    /// <summary>Checks whether the mcseq is a 'Sn' value.</summary>
    val IsSn : 'T mcseq -> bool

    /// <summary>Checks whether the mcseq is a 'Sq' value.</summary>
    val IsSq : 'T mcseq -> bool

    /// <summary>Checks whether the mcseq is a 'Null' value.</summary>
    val IsNull : 'T mcseq -> bool

    /// <summary>Converts mcseq to an array.</summary>
    val toArray : 'T mcseq -> 'T[]

    /// <summary>Converts string to its MCSeq value.</summary>
    /// <param name="x">Comma delimited string to parse.
    /// If the input string contains comma, prepend or postpend it with backslash -"\," or ",\".</param>
    /// <remarks>The method unascapes any escaped comma unless it is postpended with backslash.</remarks>
    val parse : string -> 'T mcseq


[<Class>]
/// <summary>Variable content list. Suitable for serialization of variable content
/// that can be either a single value or a list of values.</summary>
type VarList<'T,'V> when 'V :> 'T seq =
    interface IXmlSerializable

    new : unit -> VarList<'T,'V>
    new : value:'T mcseq -> VarList<'T,'V>

    /// <summary>Gets the value (<c>MCSeq<'T></c>) of the current object.</summary>
    member Value : 'T mcseq
    member internal Value : 'T mcseq with set


[<Class>]
/// <summary>Generic list with customized CollectionDataContract attribute
/// suitable for deserialization of MailChimp API array (item tag : 'struct').</summary>
type MCList<'T> =
    inherit List<'T>

    new : unit -> MCList<'T>

    /// <summary>Creates MCList from a sequence.</summary>
    static member ofSeq : xs:'T seq -> MCList<'T>


[<Class>]
/// <summary>Generic list with customized CollectionDataContract attribute
/// suitable for deserialization of Mandrill API array (item tag: 'value').</summary>
type MVList<'T> =
    inherit List<'T>

    new : unit -> MVList<'T>

    /// <summary>Creates STSList from a sequence.</summary>
    static member ofSeq : xs:'T seq -> MVList<'T>


[<Class>]
/// <summary>Generic dictionary implementing IXmlSerializable interface.
/// <para>Suitable for deserialization of MailChimp API array where keys are
/// the local names of its elements.</para></summary>
type MCDict<'T> =
    inherit Dictionary<string,'T>
    interface IXmlSerializable

    new : unit -> MCDict<'T>
    new : dict:IDictionary<string,'T> -> MCDict<'T>

    /// <summary>Creates MCDict from a sequence.</summary>
    static member ofSeq : xs:(string * 'T) seq -> MCDict<'T>


[<Class>]
/// <summary>Generic dictionary implementing IXmlSerializable interface.
/// <para>Suitable for deserialization of MailChimp API array where keys are
/// its elements attribute named "key".</para></summary>
type MCDict<'K,'T> when 'K : equality =
    inherit System.Collections.Generic.Dictionary<'K,'T>
    interface IXmlSerializable

    new : unit -> MCDict<'K,'T>
    new : dict:IDictionary<'K,'T> -> MCDict<'K,'T>

    /// <summary>Creates MCDict from a sequence.</summary>
    static member ofSeq : xs:('K * 'T) seq -> MCDict<'K,'T>


open System.Collections.Specialized

[<Class>]
/// <summary>Nested dictionary. A collection of associated String keys and values
/// that can be either String or another NestedDict.
/// <para>This type is used to parse nested query strings.</para></summary>
type NestedDict =
    inherit System.Collections.Generic.Dictionary<string, NestedDictValue>

    new : unit -> NestedDict

    /// <summary>Gets the value associated with the specified path.</summary>
    /// <param name="path">The path to the entry that contains the value to get.
    /// <para>Path format: "key0/key1/key2". Slash ('/') in the key name should be
    /// replaced with double slash ('//')</para></param>
    member Get : path:string -> NestedDictValue

    /// <summary>Gets the inner dict associated with the specified path.</summary>
    /// <param name="path">The path to the entry that contains the value to get.
    /// <para>Path format: "key0/key1/key2". Slash ('/') in the key name should be
    /// replaced with double slash ('//')</para></param>
    member GetDict : path:string -> NestedDict

    /// <summary>Gets the inner dict associated with the specified path.</summary>
    /// <param name="path">The path to the entry that contains the value to get.
    /// <para>Path format: "key0/key1/key2". Slash ('/') in the key name should be
    /// replaced with double slash ('//')</para></param>
    member GetValue : path:string -> string

    /// <summary>Parses nested query string into a NestedDict.</summary>
    static member ParseQueryString: x:string * ?encoding:System.Text.Encoding -> NestedDict

    /// <summary>Converts a NameValueCollection into a NestedDict.</summary>
    static member ParseQueryString: x:NameValueCollection -> NestedDict

/// <summary>Nested dictionary entry value.</summary>
/// <remarks>Implements explicit/implicit convertion operators.</remarks>
and NestedDictValue =
    /// <summary>The representation of empty node.</summary>
    | Empty
    /// <summary>The representation of <c>String</c> value.</summary>
    | Value of string
    /// <summary>The representation of <c>NestedDict</c> value.</summary>
    | Dict of NestedDict

    /// <summary>Implicit convertion from String.</summary>
    static member op_Implicit : x:string -> NestedDictValue

    /// <summary>Implicit convertion from NestedDict.</summary>
    static member op_Implicit : x:NestedDict -> NestedDictValue

    /// <summary>Explicit convertion to String.</summary>
    /// <exception cref="System.InvalidOperationException">Thrown when the value is not 'Value' type.</exception>
    static member op_Explicit : x:NestedDictValue -> string

    /// <summary>Explicit convertion to String.</summary>
    /// <exception cref="System.InvalidOperationException">Thrown when the value is not 'Dict' type.</exception>
    static member op_Explicit : x:NestedDictValue -> NestedDict

//------------------------------------------------------------------------------
//  (*) Misc
//------------------------------------------------------------------------------

/// <summary>Sort direction.</summary>
type SortDirection = Asc = 0 | Desc = 1

//==================================================================================
//  Api Error
//==================================================================================

/// <summary>API error base class.</summary>
type Error =

    internal new : unit -> Error
    internal new : msg:string * code:int -> Error

    /// <summary>Gets a message that describes the current exception.</summary>
    abstract Message : string

    /// <summary>Gets a value that is assigned to a specific exception.</summary>
    abstract Code : int

and
 MCException =
    inherit Exception

    internal new : e:Error * ex:exn * ?response:string -> MCException
    new : e:Error * ex:exn -> MCException

    /// <summary>Gets the error that caused the current exception.</summary>
    member Error : Error

    /// <summary>Gets the response that the remote host returned.</summary>
    member Response : string


[<AutoOpen>]
[<CompilationRepresentation(CompilationRepresentationFlags.ModuleSuffix)>]
/// <summary>Common exceptions module.</summary>
module internal MCException =

    /// <summary>Deserialization exception.</summary>
    val deserializeFail : Exception -> string -> 'T

    /// <summary>XML transform exception.</summary>
    val transformFail : Exception -> string -> 'T

    /// <summary>Server exception exception.</summary>
    val callServerFail : Exception -> int -> string -> 'T

//==================================================================================
//  Api base
//==================================================================================

/// <summary>Web method. Either GET or POST.</summary>
type WebMethod = Get = 0 | Post = 1

/// <summary>Serialization format.</summary>
type Format =
    | json = 0
    | xml = 1
    | yaml = 2
    | php = 3
    | lolcode = 4


[<AbstractClass>]
/// <summary>MailChimp API base class.</summary>
/// <remarks>Provides common functionality to all MailChimp APIs.</remarks>
type ApiBase =

    new : apiKey:string * ?secure:bool * ?timeout:int -> ApiBase

    /// <summary>Gets the value of the User-agent HTTP header.</summary>
    abstract member UserAgent : string

    /// <summary>Gets the API url format.</summary>
    /// <remarks>A url may contain the next format strings:
    /// <para>- {dc} - data center;</para>
    /// <para>- {method} - method name;</para>
    /// <para>- {fmt} - output format.</para></remarks>
    abstract member ApiUrl : string

    /// <summary>Gets the default output format.</summary
    abstract member OutputFormat : Format

    /// <summary>Gets the API key parameter name.</summary
    abstract member KeyParam : string

    /// <summary>Gets or sets the key to access the API with.</summary>
    member ApiKey : string with get, set

    /// <summary>Gets or sets a value indicating whether to turn HTTPS on.</summary>
    member Secure : bool with get, set

    /// <summary>Gets or sets the web request time-out value in seconds.</summary>
    /// <remarks>Defaults to 300.</remarks>
    member Timeout : int with get, set

    /// <summary>Gets a value indicating whether
    /// the API url has data center parameter.</summary>
    member HasDataCenter : bool

    /// <summary>Gets the data center retrieved from the supplied API key.</summary>
    member DataCenter : string

    /// <summary>Executes serial call to the server.</summary>
    /// <remarks>C# specific optional parameters.</remarks>
    /// <param name="webMethod">The method for the request.</param>
    /// <param name="methodName">The name of method to call.</param>
    /// <param name="outFmt">[optional] Output format.</param>
    /// <param name="p">[optional] The data for the request.</param>
    /// <returns>A raw http response.</returns>
    member GetResponse : webMethod:WebMethod * methodName:string * outFmt:Format opt *
        p:Input opt -> HttpWebResponse

    /// <summary>Executes serial call to the server.</summary>
    /// <remarks>F# specific optional parameters.</remarks>
    /// <param name="webMethod">The method for the request.</param>
    /// <param name="methodName">The name of method to call.</param>
    /// <param name="outFmt">[optional] Output format.</param>
    /// <param name="p">[optional] The data for the request.</param>
    /// <returns>A raw http response.</returns>
    member GetResponse : webMethod:WebMethod * methodName:string * ?outFmt:Format *
        ?p:Input -> HttpWebResponse

    /// <summary>Executes serial call to the server.</summary>
    /// <remarks>C# specific optional parameters.</remarks>
    /// <param name="webMethod">The method for the request.</param>
    /// <param name="methodName">The name of method to call.</param>
    /// <param name="outFmt">[optional] Output format.</param>
    /// <param name="p">[optional] The data for the request.</param>
    /// <returns>The status of the server's response and its content.</returns>
    member CallServer : webMethod:WebMethod * methodName:string * outFmt:Format opt *
        p:Input opt -> HttpStatusCode * string

    /// <summary>Executes serial call to the server.</summary>
    /// <remarks>F# specific optional parameters.</remarks>
    /// <param name="webMethod">The method for the request.</param>
    /// <param name="methodName">The name of method to call.</param>
    /// <param name="outFmt">[optional] Output format.</param>
    /// <param name="p">[optional] The data for the request.</param>
    /// <returns>The status of the server's response and its content</returns>
    member CallServer : webMethod:WebMethod * methodName:string * ?outFmt:Format *
        ?p:Input -> HttpStatusCode * string